跳到主要内容

图像卷积

互相关运算

严格来说,卷积层是个错误的叫法,因为它所表达的运算其实是互相关运算(cross-correlation),而不是卷积运算。卷积层中,输入张量和核张量通过互相关运算产生输出张量。

互相关运算(Cross-correlation)与卷积运算非常相似,但有一个关键的区别:在互相关中,我们不会翻转滤波器。在深度学习和卷积神经网络中,当我们说“卷积层”时,实际上通常指的是互相关运算,因为在这种情境下,不翻转滤波器并不会影响学习的能力。

数学描述

对于一个二维输入图像 SS 和一个二维滤波器 KK,互相关运算可以表示为:

(SK)(i,j)=mnS(i+m,j+n)K(m,n)(S \star K)(i, j) = \sum_{m} \sum_{n} S(i+m, j+n) K(m, n)

其中,iijj 是输出特征图的位置。与卷积不同,这里我们不需要翻转滤波器 KK

图形描述

考虑一个二维图像和一个二维滤波器。互相关运算可以被视为滤波器在图像上滑动,并在每个位置计算它们的点积。这与卷积操作非常相似,但不需要翻转滤波器。

下面一个二维图像和滤波器的图像,并展示互相关运算的结果。

如下图所示:

互相关表示

从左到右分别是:

  1. 输入图像:一个二维的图像。
  2. 滤波器:一个二维的滤波器,用于在输入图像上进行互相关运算。
  3. 互相关输出:互相关运算的结果。这是通过将滤波器在输入图像上滑动并计算点积得到的。

您可以看到,互相关运算的结果与卷积运算非常相似,但由于不需要翻转滤波器,计算过程略有不同。在深度学习中,这种操作允许我们从输入数据中提取有用的特征,这些特征随后用于各种任务,如图像分类或物体检测。

在 PyTorch 中使用

以下是如何在 PyTorch 中使用 nn.Conv2d 进行互相关运算的例子:

import torch
import torch.nn as nn

# 定义输入图像和滤波器
input_image = torch.randn(1, 1, 10, 10) # Batch size = 1, Channels = 1, Height = 10, Width = 10
filter_kernel = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=0, bias=False)

# 执行互相关运算
output = filter_kernel(input_image)

nn.Conv2d 的实现原理

nn.Conv2d 是 PyTorch 中用于执行二维卷积(实际上是互相关)的类。以下是其背后的基本原理和实现细节:

基本原理

  1. 滑动窗口nn.Conv2d 使用一个滑动窗口来遍历输入图像。这个窗口的大小与滤波器的大小相同。

  2. 点积运算:在每个位置,滤波器与其覆盖的输入图像部分进行点积运算。这意味着将滤波器的每个元素与对应的输入图像部分的元素相乘,然后将这些乘积加起来。

  3. 偏置:如果定义了偏置(bias=True),则将其添加到点积的结果中。

  4. 步长和填充nn.Conv2d 允许您定义步长(stride)和填充(padding)。步长决定了滑动窗口在图像上移动的速度,而填充决定了在输入图像的边界添加的零的数量。

实现细节

  1. 权重和偏置nn.Conv2d 有两个主要的参数:权重和偏置。权重对应于滤波器的值,而偏置是一个可选参数,可以添加到卷积的输出中。

  2. 多通道输入和输出nn.Conv2d 支持多通道输入和输出。对于多通道输入,每个输入通道都有一个对应的滤波器,所有这些滤波器的输出会被加起来,形成一个单通道的输出。对于多通道输出,每个输出通道都有一组滤波器,用于从输入图像中提取特征。

  3. 前向传播:当调用 nn.Conv2d 的实例时,它会执行前向传播,计算卷积的输出。

  4. 反向传播nn.Conv2d 也支持反向传播。当计算损失函数的梯度时,nn.Conv2d 会计算其权重和偏置的梯度,并更新它们以优化模型。

代码实现

虽然 PyTorch 的内部实现使用了高度优化的库(如 cuDNN),但以下是一个简化的 Python 代码,展示了 nn.Conv2d 的基本工作原理:

def conv2d(input, weight, bias=None, stride=1, padding=0):
# 获取输入和权重的形状
N, C, H, W = input.shape
out_channels, in_channels, kernel_height, kernel_width = weight.shape

# 计算输出的形状
out_height = (H - kernel_height + 2 * padding) // stride + 1
out_width = (W - kernel_width + 2 * padding) // stride + 1

# 初始化输出
output = torch.zeros(N, out_channels, out_height, out_width)

# 遍历每个图像、输出通道、高度和宽度
for n in range(N):
for out_channel in range(out_channels):
for i in range(0, out_height * stride, stride):
for j in range(0, out_width * stride, stride):
# 计算点积
output[n, out_channel, i//stride, j//stride] = \
(input[n, :, i:i+kernel_height, j:j+kernel_width] * weight[out_channel]).sum()
# 添加偏置
if bias is not None:
output[n, out_channel, i//stride, j//stride] += bias[out_channel]
return output

请注意,这只是一个简化的实现,用于说明 nn.Conv2d 的工作原理。在实际应用中,PyTorch 的实现使用了更高效的算法和库。

提示

在 Python 中,// 是一个整数除法(也称为地板除法)操作符。它返回两个数相除的商,但结果是一个整数,即舍去了小数部分。

例如:

  • 7 // 3 返回 2
  • -7 // 3 返回 -3
  • 7.0 // 3 返回 2.0

这与普通的除法操作符 / 不同,后者返回一个浮点数,即使两个操作数都是整数。

例如:

  • 7 / 3 返回 2.3333333333333335

在上述 conv2d 函数中,使用 // 确保输出的形状(out_heightout_width)是整数,因为我们不能有部分像素或不完整的像素。